// Box.h
//
#ifndef BOX_ONCE
#define BOX_ONCE

#include "Math.h"

namespace glm{

class box3
{
private:
	dvec3 _minPoint;
	dvec3 _maxPoint;

public:
	// -----------------------------------------------------------------------------------------------
	box3()									   : _minPoint(1.0),_maxPoint(-1.0)  {}
	box3(const dvec3 &minP, const dvec3 &maxP) : _minPoint(minP),_maxPoint(maxP) {}
	~box3(){};

	// -----------------------------------------------------------------------------------------------
	dvec3 sizes() const	{ return _maxPoint-_minPoint; }

	// -----------------------------------------------------------------------------------------------
	void reset() { _minPoint = OpenGL::Math::dvec3(1.0); _maxPoint = OpenGL::Math::dvec3(-1.0);}

	// -----------------------------------------------------------------------------------------------
	void setMinPoint( const dvec3 &p ) { _minPoint = p; }

	// -----------------------------------------------------------------------------------------------
	void setMaxPoint( const dvec3 &p ) { _maxPoint = p; }

	// -----------------------------------------------------------------------------------------------
	dvec3 minPoint() const { return _minPoint; }
	dvec3 maxPoint() const { return _maxPoint; }

	// -----------------------------------------------------------------------------------------------
	bool empty(double tolerance = 1e-8) const 
	{
		const bool notValid = (_minPoint.x > _maxPoint.x && _minPoint.y > _maxPoint.y && _minPoint.z > _maxPoint.z);	
		const bool tooSmall = false; // length(_maxPoint-_minPoint) <= tolerance;
		return notValid || tooSmall;
	}

	// -----------------------------------------------------------------------------------------------
	dvec3 center() const {	return (_minPoint+_maxPoint) * dvec3(0.5);	}

	// -----------------------------------------------------------------------------------------------
	double diag() const { return distance(_maxPoint,_minPoint); }
	
	// -----------------------------------------------------------------------------------------------
	box3 &extend(const dvec3 &point)
	{
		if( empty() )
			_minPoint = _maxPoint = point;
		else
		{
			for (int i = 0; i < 3; i++)
			{
				if (_minPoint[i] > point[i]) _minPoint[i] = point[i];
				if (_maxPoint[i] < point[i]) _maxPoint[i] = point[i];
			}
		}
		return *this;
	}

	// -----------------------------------------------------------------------------------------------
	box3 &extend(const box3 &box)
	{
		if( empty() )
		{
			_minPoint = box._minPoint;
			_maxPoint = box._maxPoint;
		}
		else
		{
			for (int i = 0; i < 3; i++)
			{
				if (_minPoint[i] > box._minPoint[i]) _minPoint[i] = box._minPoint[i];
				if (_maxPoint[i] < box._maxPoint[i]) _maxPoint[i] = box._maxPoint[i];
			}
		}
		return *this;
	}

	// -----------------------------------------------------------------------------------------------
	void transform( const dmat4 &m )
	{
		dvec4 minP(_minPoint,1.0); minP = m*minP;
		dvec4 maxP(_maxPoint,1.0); maxP = m*maxP;

		_minPoint = dvec3(minP);
		_maxPoint = dvec3(maxP);
	}

	// -----------------------------------------------------------------------------------------------
	box3 transformed( const dmat4 &m ) const
	{
		dvec4 minP(_minPoint,1.0); minP = m*minP;
		dvec4 maxP(_maxPoint,1.0); maxP = m*maxP;

		return box3( dvec3(minP),dvec3(maxP) );
	}

	// -----------------------------------------------------------------------------------------------
	void translate( const dvec3 &t )
	{
		_minPoint += t;	_maxPoint += t;		
	}

	// -----------------------------------------------------------------------------------------------
	box3 translated( const dvec3 &t ) const
	{
		return box3(_minPoint+t,_maxPoint+ t);
	}

	// -----------------------------------------------------------------------------------------------
	box3 rotated( double angle, double nx, double ny, double nz ) const
	{
		dmat4 r  = glm::rotate( dmat4(1.0), angle, dvec3(nx,ny,nz) );
		
		dvec4 p1(_minPoint,1.0);
		dvec4 p2(_maxPoint,1.0);

		p1 = r*p1; p2 = r*p2;
		
		return box3( dvec3(p1), dvec3(p2) );	
	}

	// -----------------------------------------------------------------------------------------------
	void rotate( double angle, double nx, double ny, double nz )
	{
		dmat4 r  = glm::rotate( dmat4(1.0), glm::radians(angle), dvec3(nx,ny,nz) );
		
		dvec4 p1(_minPoint,1.0);
		dvec4 p2(_maxPoint,1.0);

		p1 = r*p1; p2 = r*p2;
		
		_minPoint = dvec3(p1);
		_maxPoint = dvec3(p2);
	}

	// -----------------------------------------------------------------------------------------------
	box3 scaled( double s ) const
	{
		dvec3 ss(s);
		dvec3 c( center() );
		dvec3 p0( (_minPoint-c)*s );
		dvec3 p1( (_maxPoint-c)*s );

		return box3( p0+c,p1+c);
	}

	// -----------------------------------------------------------------------------------------------
	void scale( double s )
	{
		dvec3 c( center() );
		dvec3 p0( (_minPoint-c)*s );
		dvec3 p1( (_maxPoint-c)*s );

		_minPoint = p0+c;
		_maxPoint = p1+c;
	}

	// -----------------------------------------------------------------------------------------------
	void scale( double xs, double ys, double zs )
	{
		dvec3 s(xs,ys,zs);
		dvec3 c( center() );
		dvec3 p0( (_minPoint-c)*s );
		dvec3 p1( (_maxPoint-c)*s );

		_minPoint = p0+c;
		_maxPoint = p1+c;
	}

	// -----------------------------------------------------------------------------------------------
	void scaleToFit( const box3 &target )
	{
		const double w  =  sizes().x;
		const double h  =  sizes().y;
		const double tw =  target.sizes().x;
		const double th =  target.sizes().y;
		
		const double ar  = w/h;
		const double tAr = tw/th;

		// scale this box according to the aspect ratio
		if( tAr > ar ) scale(th/h);
		else           scale(tw/w);
	}


	// -----------------------------------------------------------------------------------------------
	static box3 createAABB( const box3 &b )
	{
		return createAABB( b._minPoint,b._maxPoint );
	}

	// -----------------------------------------------------------------------------------------------
	static box3 createAABB( const dvec3 &p0, const dvec3 &p1 )
	{
		box3 ret;
		
		ret._minPoint = p0;
		ret._maxPoint = p0;

		for( int i=0; i<3; ++i )
		{
			if( p0[i] < ret._minPoint[i] ) ret._minPoint[i] = p0[i];
			if( p1[i] < ret._minPoint[i] ) ret._minPoint[i] = p1[i];

			if( p0[i] > ret._maxPoint[i] ) ret._maxPoint[i] = p0[i];
			if( p1[i] > ret._maxPoint[i] ) ret._maxPoint[i] = p1[i];
		}

		return ret;
	}

	// -----------------------------------------------------------------------------------------------
	void setSizes( const dvec3 sizes )
	{
		_maxPoint = _minPoint + sizes;
	}

	//// -----------------------------------------------------------------------------------------------
	//box3 &scale( const dvec3 &t )
	//{
	//	return *this;
	//}

	//// -----------------------------------------------------------------------------------------------
	//box3 &rotate( double angle, const dvec3 &axis )
	//{
	//	mat4 r  = glm::rotate( mat4(1.0), angle, axis );
	//	
	//	dvec4 p1(_minPoint,1.0);
	//	dvec4 p2(_maxPoint,1.0);

	//	p1 = r*p1; p2 = r*p2;
	//	
	//	_minPoint = dvec3(p1);
	//	_maxPoint = dvec3(p2);
	//	return *this;
	//}

	// -----------------------------------------------------------------------------------------------
	bool contains(const box3 & box) const
	{
		return contains(box.minPoint()) && contains(box.maxPoint());
	}


	// -----------------------------------------------------------------------------------------------
	bool contains(const dvec3 &point) const
	{
		if (_minPoint.x > point.x) return false;
		if (_minPoint.y > point.y) return false;
		if (_minPoint.z > point.z) return false;
		if (_maxPoint.x < point.x) return false;
		if (_maxPoint.y < point.y) return false;
		if (_maxPoint.z < point.z) return false;
		return true;
	}

	// -----------------------------------------------------------------------------------------------
	bool contains(const dvec2 &point) const
	{
		if (_minPoint.x > point.x) return false;
		if (_minPoint.y > point.y) return false;
		if (_maxPoint.x < point.x) return false;
		if (_maxPoint.y < point.y) return false;
		return true;
	}

	///// Tests if the 2D projection of the box contains the 2D projection of the given point, within the given tolerance.
	//bool contains2D(const IDSReal3D &point, REAL tolerance = 0.) const {
	//	for (int i = 0; i < 2; i++) {
	//		if (minCoord[i] - tolerance > point[i]) return false;
	//		if (maxCoord[i] + tolerance < point[i]) return false;
	//	}
	//	return true;
	//}

	bool overlaps(const box3 &box, double tolerance = 1e-8) const
	{
		for(int i = 0; i < 3; ++i)
		{
			if( _minPoint[i] - 2.0*tolerance > box._maxPoint[i] ) return false;
			if( _maxPoint[i] + 2.0*tolerance < box._minPoint[i] ) return false;
		}
		return true;
	}

	// Tests if the boxes (projected on plane z=0) overlap within the given tolerance.
	bool overlaps2D(const box3 &box, double tolerance = 1e-8) const
	{
		for(int i = 0; i < 2; ++i)
		{
			if( _minPoint[i] - 2.0*tolerance > box._maxPoint[i] ) return false;
			if( _maxPoint[i] + 2.0*tolerance < box._minPoint[i] ) return false;
		}
		return true;
	}

	///// Returns the squared distance fron a point.
	//REAL squaredDistance(const IDSReal3D &point) const {
	//	REAL dist2 = 0;
	//	for (int i = 0; i < 3; i++) {
	//		REAL r = minCoord[i] - point[i];
	//		if (r > 0) dist2 += r*r;
	//		r = point[i] - maxCoord[i];
	//		if (r > 0) dist2 += r*r;
	//	}
	//	return dist2;
	//}


};

}

#endif